# TODO: Add checks
# TODO: Implement all getters
# TODO: Add schemas, namespaces and XSL
# TODO: Add multiline docs with """
import time
import re

re_id = re.compile(r'^[a-zA-Z0-9_\-@.]+$')
re_date = re.compile(r'^\d{4}-\d{2}-\d{2}$')
re_date_time = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$')
NS = 'http://sample.com/ns'

def get_today ():
	return time.strftime('%Y-%m-%d')

class ExistsException(Exception): pass
class NotFoundException(Exception): pass
class InvalidStateException(Exception): pass
class InvalidFormatException(Exception): pass

class XmlAwareList(list):
	def __init__(self, node):
		self.node = node
	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), self.node, '')
		if root: node.setProp('xmlns', NS)
		for item in self:
			node.addChild(item.toXml(doc))
		return node

class Users:
	def __init__(self, users):
		self.__users = users
		for user in self.__users: user.parent = self

	def getUser(self, id):
		for user in self.__users:
			if user.getId() == id:
				return user

	def createUser(self, user):
		if getUser(user.getId()):
			raise ExistsException()
		else:
			user.parent = self
			self.__users += [user]

	def deleteUser(self, id):
		user = getUser(user.getId())
		if user:
			self.__users.remove(user)
			return user
		else:
			raise NotFoundException()

	def getUsers(self):
		return self.__users

	@staticmethod
	def fromXml(node):
		users = [User.fromXml(child) for child in node.get_children() if child.get_name() == 'user']
		return Users(users)

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'users', '')
		if root: node.setProp('xmlns', NS)
		for user in self.__users:
			node.addChild(user.toXml(doc))
		return node

class User:

	def __init__(self, id, name, password, regdate, pomodoroLen, breakLen, plans):
		self.setId(id)
		self.setName(name)
		self.setPassword(password)
		self.setRegdate(regdate)
		self.setPomodoroLen(pomodoroLen)
		self.setBreakLen(breakLen)
		self.__plans = XmlAwareList('plans')
		self.__plans += plans
		for plan in self.__plans: plan.parent = self

	def setId(self, id):
		if not re_id.match(id):
			raise InvalidFormatException()
		self.__id = id

	def setName(self, name):
		if not name:
			raise InvalidFormatException()
		self.__name = name

	def setPassword(self, password):
		if not password:
			raise InvalidFormatException()
		self.__password = password

	def setRegdate(self, regdate):
		if not re_date_time.match(regdate):
			raise InvalidFormatException()
		self.__regdate = regdate

	def setPomodoroLen(self, pomodoroLen):
		if int(pomodoroLen) <= 0:
			raise InvalidFormatException()
		self.__pomodoroLen = pomodoroLen

	def setBreakLen(self, breakLen):
		if int(breakLen) <= 0:
			raise InvalidFormatException()
		self.__breakLen = breakLen

	def getId(self):
		return self.__id

	def getName(self):
		return self.__name

	def getPassword(self):
		return self.__password

	def getRegdate(self):
		return self.__regdate

	def getPomodoroLen(self):
		return self.__pomodoroLen

	def getBreakLen(self):
		return self.__breakLen

	# TODO: Modify other fromXml in the same way
	@staticmethod
	def fromXml(node):
		plans = [Plan.fromXml(child) for child in node.get_children() if child.get_name() == 'plan']
		return User(node.prop('id'), node.prop('name'), node.prop('password'), node.prop('regdate'), node.prop('pomodoro-length'), node.prop('break-length'), plans)

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'user', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('id', self.__id)
		node.setProp('name', self.__name)
		node.setProp('password', self.__password)
		node.setProp('regdate', self.__regdate)
		node.setProp('pomodoro-length', self.__pomodoroLen)
		node.setProp('break-length', self.__breakLen)
		for plan in self.__plans:
			node.addChild(plan.toXml(doc))
		return node

	def getAllPlans(self):
		return self.__plans

	def getPlansForYear(self, year):
		plans = XmlAwareList('plans')
		plans += [plan for plan in self.getAllPlans() if plan.getDate().split('-')[0] == year]
		return plans

	def getPlansForMonth(self, year, month):
		plans = XmlAwareList('plans')
		plans += [plan for plan in self.getAllPlans() if plan.getDate().split('-')[0] == year and plan.getDate().split('-')[1] == month]
		return plans

	def getPlan(self, year, month, day):
		for plan in self.getAllPlans():
			if plan.getDate() == "%s-%s-%s" % (year, month, day):
				return plan

	def createPlan(self, plan):
		[year, month, day] = plan.getDate().split('-')
		if getPlan(year, month, day):
			raise ExistsException()
		else:
			plan.parent = self
			self.__plans += [plan]

	def deletePlan(self, date):
		[year, month, day] = self.getDate().split('-')
		plan = getPlan(year, month, day)
		if plan:
			self.__plans.remove(plan)
			return plan
		else:
			raise NotFoundException()

class Plan:
	def __init__(self, date, status, workItems):
		self.setDate(date)
		self.setStatus(status)
		self.__workItems = XmlAwareList('workitems')
		self.__workItems += workItems
		for item in self.__workItems: item.parent = self

	def setDate(self, date):
		if not re_date.match(date):
			raise InvalidFormatException()
		self.__date = date

	def setStatus(self, status):
		if not status in ['new', 'started', 'completed', 'failed']:
			raise InvalidFormatException()
		self.__status = status

	def getDate(self):
		return self.__date

	@staticmethod
	def fromXml(node):
		items = [WorkItem.fromXml(child) for child in node.get_children() if child.get_name() == 'workitem']
		return Plan(node.prop('date'), node.prop('status'), items)

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'plan', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('date', self.getDate())
		node.setProp('status', self.getStatus())
		for workItem in self.__workItems:
			node.addChild(workItem.toXml(doc))
		return node

	def createWorkItem(self, workItem):
		workItem.parent = self
		self.__workItems += [workItem]

	def getStatus(self):
		if self.__status in ['completed', 'failed']: return self.__status
		if self.__date == get_today():
			if [item for item in self.__workItems if item.getStatus() != 'new']:
				return 'started'
			else:
				return 'new'
		else:
			return 'failed'

	def getWorkItems(self):
		return self.__workItems

class WorkItem:
	def __init__(self, section, status, title, pomodoros):
		self.setSection(section)
		self.setStatus(status)
		self.setTitle(title)
		self.__pomodoros = XmlAwareList('pomodoros')
		self.__pomodoros += pomodoros
		for pomodoro in self.__pomodoros: pomodoro.parent = self

	def setSection(self, section):
		if not section in ['unplanned', 'planned']:
			raise InvalidFormatException()
		self.__section = section

	def setStatus(self, status):
		if not status in ['new', 'started', 'completed', 'failed']:
			raise InvalidFormatException()
		self.__status = status

	def setTitle(self, title):
		if not title:
			raise InvalidFormatException()
		self.__title = title

	def getSection(self):
		return self.__section
	
	def getTitle(self):
		return self.__title

	@staticmethod
	def fromXml(node):
		pomodoros = [Pomodoro.fromXml(child) for child in node.get_children() if child.get_name() == 'pomodoro']
		return WorkItem(node.prop('section'), node.prop('status'), node.prop('title'), pomodoros)

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'workitem', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('section', self.getSection())
		node.setProp('status', self.getStatus())
		node.setProp('title', self.getTitle())
		for pomodoro in self.__pomodoros:
			node.addChild(pomodoro.toXml(doc))
		return node

	def toXmlContainer(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'pomodoros', '')
		if root: node.setProp('xmlns', NS)
		for pomodoro in self.__pomodoros:
			node.addChild(pomodoro.toXml(doc))
		return node

	def createPomodoro(self, pomodoro):
		pomodoro.parent = self
		self.__pomodoros += [pomodoro]

	def getStatus(self):
		if self.__status in ['completed', 'failed']: return self.__status
		if self.parent.getDate() == get_today():
			if [pomodoro for pomodoro in self.__pomodoros if pomodoro.getStatus() != 'new']:
				return 'started'
			else:
				return 'new'
		else:
			return 'failed'

	def getPomodoros(self):
		return self.__pomodoros

class Pomodoro:
	def __init__(self, planned, status, start, finish, interruptions, messages):
		self.setPlanned(planned)
		self.setStatus(status)
		self.setStart(start)
		self.setFinish(finish)
		self.__interruptions = XmlAwareList('interruptions')
		self.__interruptions += interruptions
		self.__messages = XmlAwareList('messages')
		self.__messages += messages
		for message in self.__messages: message.parent = self
		for interruption in self.__interruptions: interruption.parent = self

	def setPlanned(self, planned):
		if not planned in ['true', 'false']:
			raise InvalidFormatException()
		self.__planned = planned

	def setStatus(self, status):
		if not status in ['new', 'started', 'completed', 'failed']:
			raise InvalidFormatException()
		self.__status = status

	def setStart(self, start):
		if not re_date_time.match(start):
			raise InvalidFormatException()
		self.__start = start

	def setFinish(self, finish):
		if not re_date_time.match(finish):
			raise InvalidFormatException()
		self.__finish = finish

	def getPlanned(self):
		return self.__planned

	def getStatus(self):
		return self.__status

	def getStart(self):
		return self.__start

	def getFinish(self):
		return self.__finish

	def getInterruptions(self):
		return self.__interruptions

	def getMessages(self):
		return self.__messages

	@staticmethod
	def fromXml(node):
		interruptions = [Interruption.fromXml(child) for child in node.get_children() if child.get_name() == 'interruption']
		messages = [Message.fromXml(child) for child in node.get_children() if child.get_name() == 'message']
		return Pomodoro(node.prop('planned'), node.prop('status'), node.prop('start'), node.prop('finish'), interruptions, messages)

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'pomodoro', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('planned', self.getPlanned())
		node.setProp('status', self.getStatus())
		node.setProp('start', self.getStart())
		node.setProp('finish', self.getFinish())
		for interruption in self.__interruptions:
			node.addChild(interruption.toXml(doc))
		for message in self.__messages:
			node.addChild(message.toXml(doc))
		return node

	def createInterruption(self, interruption):
		interruption.parent = self
		self.__interruptions += [interruption]

	def createMessage(self, message):
		message.parent = self
		self.__messages += [message]

	def getStatus(self):
		if self.__status in ['completed', 'failed']: return self.__status
		if self.parent.parent.getDate() == get_today():
			return self.__status
		else:
			return 'failed'

class Interruption:

	__start = None
	__duration = None
	__comment = None

	def __init__(self, start, duration, comment):
		if start: self.setStart(start)
		if duration: self.setDuration(duration)
		if comment: self.setComment(comment)

	def setStart(self, start):
		if not re_date_time.match(start):
			raise InvalidFormatException()
		self.__start = start

	def setDuration(self, duration):
		if int(duration) <= 0:
			raise InvalidFormatException()
		self.__duration = duration

	def setComment(self, comment):
		self.__comment = comment

	def getStart(self):
		return self.__start

	def getDuration(self):
		return self.__duration

	def getComment(self):
		return self.__comment

	@staticmethod
	def fromXml(node):
		return Interruption(node.prop('start'), node.prop('duration'), node.prop('comment'))

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'interruption', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('start', self.getStart())
		node.setProp('duration', self.getDuration())
		node.setProp('comment', self.getComment())
		return node

class Message:
	def __init__(self, user, text, when):
		self.setUser(user)
		self.setText(text)
		self.setWhen(when)

	def setUser(self, user):
		if not re_id.match(user):
			raise InvalidFormatException()
		self.__user = user

	def setText(self, text):
		if not text:
			raise InvalidFormatException()
		self.__text = text

	def setWhen(self, when):
		if not re_date_time.match(when):
			raise InvalidFormatException()
		self.__when = when

	def getUser(self):
		return self.__user

	def getText(self):
		return self.__text

	def getWhen(self):
		return self.__when

	@staticmethod
	def fromXml(node):
		return Message(node.prop('from'), node.prop('text'), node.prop('when'))

	def toXml(self, doc, root = False):
		node = doc.newDocNode(doc.ns(), 'message', '')
		if root: node.setProp('xmlns', NS)
		node.setProp('from', self.getUser())
		node.setProp('text', self.getText())
		node.setProp('when', self.getWhen())
		return node

# Test
if False:
	import libxml2
	HOMEDIR = "/home/kostya/NetBeansProjects/PomodoroServer/src"
	doc = libxml2.parseFile("%s/users.xml" % HOMEDIR)
	users = Users.fromXml(doc.getRootElement())
	print users.getUser('kostya').getPlansForYear('2009').toXml(doc)